#pragma once
#include "Precompiled.h"
#include <ReignMath.hxx>
#include <cmath>

#define GMLabwork06Implementation 0
#define GMLabwork07Implementation 0
#define GMLabwork08Implementation 1

namespace Demo
{
	template <class TBufferContent>
	inline void CreateVertexBuffer(
		_In_ ID3D11Device1 *pDevice,
		_In_ UInt32 uNumBufferElements,
		_Outref_result_nullonfailure_ ID3D11Buffer **ppBuffer,
		_Const_ TBufferContent const *pInitialData
		)
	{
		D3D11_BUFFER_DESC BufferDescriptor = { 0 };
		BufferDescriptor.ByteWidth = uNumBufferElements * GetByteSizeOf(TBufferContent);
		BufferDescriptor.BindFlags = D3D11_BIND_VERTEX_BUFFER;
		BufferDescriptor.Usage = D3D11_USAGE_IMMUTABLE;

		D3D11_SUBRESOURCE_DATA BufferInitData = { 0 };
		BufferInitData.pSysMem = pInitialData;

		Sehnsucht::ThrowIfFailed(
			//"FluidSimulator: Failed to create vertex buffer.", 
			pDevice->CreateBuffer( 
				&BufferDescriptor, 
				&BufferInitData, 
				ppBuffer 
				)
			);
	}



	template <class TBufferContent>
	inline void CreateConstantBuffer(
		_In_ ID3D11Device1 *pDevice,
		_Outref_result_nullonfailure_ ID3D11Buffer **ppBuffer,
		_Const_ _Maybenull_ TBufferContent const *pInitialData = nullptr
		)
	{
		D3D11_BUFFER_DESC BufferDescriptor = { 0 };
		BufferDescriptor.ByteWidth = GetByteSizeOf(TBufferContent);
		BufferDescriptor.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
		BufferDescriptor.Usage = D3D11_USAGE_DEFAULT;

		if ( pInitialData )
		{
			D3D11_SUBRESOURCE_DATA BufferInitData = { 0 };
			BufferInitData.pSysMem = pInitialData;

			Sehnsucht::ThrowIfFailed(
				//"FluidSimulator: Failed to create constant buffer.", 
				pDevice->CreateBuffer( 
					&BufferDescriptor, 
					&BufferInitData, 
					ppBuffer 
					)
				);
		}
		else
		{
			Sehnsucht::ThrowIfFailed(
				//"FluidSimulator: Failed to create constant buffer.", 
				pDevice->CreateBuffer( 
					&BufferDescriptor, 
					nullptr, 
					ppBuffer 
					)
				);
		}
	}

	template <class TBufferContent>
	inline void CreateStructuredBuffer(
		_In_ ID3D11Device1 *pDevice,
		_In_ UInt32 uNumBufferElements,
		_Outref_result_nullonfailure_ ID3D11Buffer **ppBuffer,
		_Outref_result_nullonfailure_ ID3D11ShaderResourceView **ppSRView,
		_Outref_result_nullonfailure_ ID3D11UnorderedAccessView **ppUAView,
		_Const_ _Maybenull_  TBufferContent const *pInitialData = nullptr
		)
	{
		D3D11_BUFFER_DESC BufferDescriptor;
		Reign::Standard::ZeroValue( &BufferDescriptor );
		BufferDescriptor.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;
		BufferDescriptor.ByteWidth = uNumBufferElements * GetByteSizeOf(TBufferContent);
		BufferDescriptor.StructureByteStride = GetByteSizeOf(TBufferContent);
		BufferDescriptor.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
		BufferDescriptor.Usage = D3D11_USAGE_DEFAULT;

		if ( pInitialData )
		{

			D3D11_SUBRESOURCE_DATA BufferInitData;
			Reign::Standard::ZeroValue( &BufferInitData );
			BufferInitData.pSysMem = pInitialData;

			Sehnsucht::ThrowIfFailed(
				//"FluidSimulator: Failed to create structured buffer.", 
				pDevice->CreateBuffer( 
					&BufferDescriptor, 
					&BufferInitData, 
					ppBuffer 
					)
				);
		}
		else
		{
			Sehnsucht::ThrowIfFailed(
				//"FluidSimulator: Failed to create structured buffer.", 
				pDevice->CreateBuffer( 
					&BufferDescriptor, 
					nullptr, 
					ppBuffer 
					)
				);
		}

		if ( ppSRView ) 
		{
			D3D11_SHADER_RESOURCE_VIEW_DESC BufferSRViewDesc;
			Reign::Standard::ZeroValue( &BufferSRViewDesc );
			BufferSRViewDesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER;
			BufferSRViewDesc.Buffer.ElementWidth = uNumBufferElements;

			Sehnsucht::ThrowIfFailed(
				//"FluidSimulator: Failed to create structured buffer srv.", 
				pDevice->CreateShaderResourceView( 
					(*ppBuffer), (&BufferSRViewDesc), 
					(ppSRView)
					)
				);
		}

		if ( ppUAView )
		{
			D3D11_UNORDERED_ACCESS_VIEW_DESC BufferUAViewDesc;
			Reign::Standard::ZeroValue( &BufferUAViewDesc );
			BufferUAViewDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
			BufferUAViewDesc.Buffer.NumElements = uNumBufferElements;
		
			Sehnsucht::ThrowIfFailed(
				//"FluidSimulator: Failed to create structured buffer uav.", 
				pDevice->CreateUnorderedAccessView( 
					(*ppBuffer), (&BufferUAViewDesc), 
					(ppUAView)
					)
				);
		}
	}
};

namespace Demo
{
	refclass Renderer : public Sehnsucht::DirectXBase
	{
#pragma region Internal types

		typedef std::vector<Reign::Vector2> PointCollection;

		typedef Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> SolidColorBrush;
		typedef Microsoft::WRL::ComPtr<ID2D1PathGeometry> PathGeometry;
		typedef Microsoft::WRL::ComPtr<ID2D1GeometrySink> GeometrySink;
		typedef Microsoft::WRL::ComPtr<IDWriteTextFormat> TextFormat;
		typedef Microsoft::WRL::ComPtr<ID2D1StrokeStyle> StrokeStyle;
		typedef Microsoft::WRL::ComPtr<ID2D1Bitmap> Bitmap;

		typedef SolidColorBrush::InterfaceType *SolidColorBrushPtr;
		typedef PathGeometry::InterfaceType *PathGeometryPtr;
		typedef GeometrySink::InterfaceType *GeometrySinkPtr;
		typedef StrokeStyle::InterfaceType *StrokeStylePtr;
		typedef TextFormat::InterfaceType *TextFormatPtr;
		typedef Bitmap::InterfaceType **BitmapAddress;
		typedef Bitmap::InterfaceType *BitmapPtr;

		typedef concurrency::task<void> EventChain;
		typedef D2D1_RECT_F TextRect;

		typedef Reign::Vector2 Point; // Point coords
		TinyTypeDecl(Point); // Point coords

#pragma endregion  Direct2D

		struct Vertex
		{
			UInt32 uControlPoint;
			DirectX::XMFLOAT4 f4Normal; 
			DirectX::XMFLOAT2 f2Tex; 
		};

		struct ControlPoint // Only position required
		{
			DirectX::XMFLOAT4 f4ControlPoint;
		};

		struct PerModel // Perfectly aligned structure (x4)
		{
			DirectX::XMMATRIX f4x4World;
			DirectX::XMMATRIX f4x4View;
			DirectX::XMMATRIX f4x4Proj;
			DirectX::XMVECTOR f4CameraPositionWorld;
		};

		struct SceneGraphNode
		{
			typedef std::vector<SceneGraphNode> SceneGraphNodeCollection;
			SceneGraphNodeCollection vnChildren;

			ID3D11DeviceContext1 *pContext;
			ID3D11Device1 *pDevice;

			Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> lTexSRV;
			Microsoft::WRL::ComPtr<ID3D11Texture2D> lTex;

			Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> lControlPointsSRV;
			Microsoft::WRL::ComPtr<ID3D11Buffer> lControlPoints;
			Microsoft::WRL::ComPtr<ID3D11Buffer> lVertices;
			DirectX::XMMATRIX f4x4World;
			UInt32 nVertex;

			void ExtractData( Eifersucht::SceneChildPtr pChild )
			{
				auto const pMesh = pChild->GetTypedChildContext<Eifersucht::MeshContext>();

				// That wont work for other scenes
				// For gm lab 9 only !

				nVertex = pMesh->vnFaces.size() * 3;
				f4x4World = pChild->LclScale;
				f4x4World *= pChild->LclRotation;
				f4x4World *= pChild->LclTranslation;
				//f4x4World *= pChild->LclRotation;
				//f4x4World *= pChild->LclScale;

				auto const nFace = pMesh->vnFaces.size();

				{
					Vertex *pVertices = new Vertex[nVertex];
					ZeroValueArray(pVertices, nVertex);

					for ( auto iFace = 0u; iFace < nFace; iFace++ ) // for each face 
					for ( auto iFaceVertex = 0; iFaceVertex < 3; iFaceVertex++) // for each vertex in face do
					{
						auto const iVertex = iFace * 3 + iFaceVertex; // vertex id
						pVertices[iVertex].uControlPoint = pMesh->vnFaces[iFace].v3u1ControlPoints[2-iFaceVertex];
						pVertices[iVertex].f4Normal = pMesh->vnFaces[iFace].v3f4Normals[2-iFaceVertex];
						pVertices[iVertex].f2Tex = pMesh->vnFaces[iFace].v3f2TexCoords[2-iFaceVertex];
					}

					Demo::CreateVertexBuffer(
						pDevice, nVertex, 
						lVertices.GetAddressOf(), 
						pVertices
						);

					Safe_DeleteArray(pVertices);
				}

				{
					auto nControlPoint = pMesh->vnControlPoints.size();
					ControlPoint *pControlPoints  = new ControlPoint[nControlPoint];
					ZeroValueArray(pControlPoints, nControlPoint);

					for ( auto iPoint = 0u; iPoint < nControlPoint; iPoint++ )
					{
						pControlPoints[iPoint].f4ControlPoint = pMesh->vnControlPoints[iPoint].f4Position;
					}

					Demo::CreateStructuredBuffer(
						pDevice, nControlPoint, lControlPoints.GetAddressOf(), 
						lControlPointsSRV.GetAddressOf(), 
						nullptr, pControlPoints
						);

					Safe_DeleteArray(pControlPoints);
				}

				{
					String16 szName = pChild->GetName();
					String16 szTex = L"ModelImageTexGray.bmp";
					
					if ( szName == L"Model_Box_Back")				szTex = L"doska23.jpg"; else
					if ( szName == L"Model_Box_Front")				szTex = L"doska22.jpg"; else
					if ( szName == L"Model_Wheel_FrontLeft")		szTex = L"Model_Color_Red.png"; else
					if ( szName == L"Model_Wheel_FrontRight")		szTex = L"Model_Color_Red.png"; else
					if ( szName == L"Model_Wheel_BackRight")		szTex = L"Model_Color_Red.png"; else
					if ( szName == L"Model_Wheel_BackLeft")			szTex = L"Model_Color_Red.png"; else
					if ( szName == L"Model_UpperBase")				szTex = L"Model_Color_Blue.png"; else
					if ( szName == L"Model_LowerBase")				szTex = L"Model_Color_Blue.png"; else
					if ( szName == L"Model_WayTo")					szTex = L"Model_Color_Pink.png"; else
					if ( szName == L"Model_Floor")					szTex = L"Model_Color_Yellow.png"; else
					if ( szName == L"Model_WayUp")					szTex = L"Model_Color_Pink.png";

					Sehnsucht::IO::BasicLoader Loader(pDevice);
					Loader.LoadTexture( 
						szTex.data(), lTex.GetAddressOf(), 
						lTexSRV.GetAddressOf() 
						);
				}
			}

			void AttachRecursively( Eifersucht::SceneChildPtr pChild )
			{
				auto nChildren = pChild->GetChildrenCount();
				for ( auto iChild = 0u; iChild < nChildren; iChild++ )
				{
					SceneGraphNode lNode;
					lNode.pDevice = pDevice;
					lNode.pContext = pContext;
					lNode.ExtractData(*pChild->GetChildAt(iChild));

					vnChildren.push_back(lNode);
				}
			}

			void DrawRecursively( PerModel *pPerModelContext, ID3D11Buffer *pPerModelBuffer )
			{
				static UInt32 s_uStride = sizeof Vertex;
				static UInt32 s_uOffset = 0;

				pPerModelContext->f4x4World = f4x4World;
				pContext->UpdateSubresource(pPerModelBuffer, 0, nullptr, pPerModelContext, 0, 0 );
				pContext->IASetVertexBuffers(0, 1, lVertices.GetAddressOf(), &s_uStride, &s_uOffset);
				pContext->VSSetShaderResources(0, 1, lControlPointsSRV.GetAddressOf());
				if (lTexSRV.Get()) pContext->PSSetShaderResources(0, 1, lTexSRV.GetAddressOf());
				pContext->Draw(nVertex, 0);

				for ( auto &rNode : vnChildren )
					rNode.DrawRecursively( 
						pPerModelContext, 
						pPerModelBuffer 
						);
			}
		};

		struct SceneGraph
		{
			typedef std::vector<SceneGraphNode> SceneGraphNodeCollection;
			SceneGraphNodeCollection vnNode;

			ID3D11DeviceContext1 *pContext;
			ID3D11Device1 *pDevice;

			void AttachRecursively( Eifersucht::SceneChildPtr pChild )
			{
				auto nChildren = pChild->GetChildrenCount();
				for ( auto iChild = 0u; iChild < nChildren; iChild++ )
				{
					SceneGraphNode lNode;
					lNode.pDevice = pDevice;
					lNode.pContext = pContext;
					lNode.ExtractData(*pChild->GetChildAt(iChild));
					lNode.AttachRecursively(*pChild->GetChildAt(iChild));

					vnNode.push_back(lNode);
				}
			}

			void DrawRecursively( PerModel *pPerModelContext, ID3D11Buffer *pPerModelBuffer )
			{
				for ( auto &rNode : vnNode )
				{
					rNode.DrawRecursively( 
						pPerModelContext, 
						pPerModelBuffer 
						);
				}
			}
		};

	internal:

		Renderer();
		void CreateBuffers();
		void CreateShaders();

		void OnFrameMove( _In_ TimerViewPtr pTimer );
		void OnFrame();

		Void OnPointerWheelChanged( _In_ Int16 iWheelDelta );

		Void OnPointerMoved( _In_ Int16 iX, _In_ Int16 iY );
		Void OnPointerLeftButtonPressed( _In_ Int16 iX, _In_ Int16 iY );
		Void OnPointerRightButtonPressed( _In_ Int16 iX, _In_ Int16 iY );
		Void OnPointerMiddleButtonPressed( _In_ Int16 iX, _In_ Int16 iY );

		Void OnPointerLeftButtonReleased();
		Void OnPointerRightButtonReleased();
		Void OnPointerMiddleButtonReleased();

	protectedprivate:

		Microsoft::WRL::ComPtr<ID3D11SamplerState> m_ScenePsSampler;
		Microsoft::WRL::ComPtr<ID3D11Buffer> m_SceneVertexBuffer;
		Microsoft::WRL::ComPtr<ID3D11Buffer> m_PerModelConstBuffer;
		Microsoft::WRL::ComPtr<ID3D11Buffer> m_ControlPointsStructBuffer;
		Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_ControlPointsStructBufferSRV;

		Microsoft::WRL::ComPtr<ID3D11Texture2D> m_ModelTex;
		Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_ModelTexSRV;

		Microsoft::WRL::ComPtr<ID3D11PixelShader> m_ScenePs;
		Microsoft::WRL::ComPtr<ID3D11VertexShader> m_SceneVs;
		Microsoft::WRL::ComPtr<ID3D11InputLayout> m_SceneVsLayout;

		Eifersucht::Scene m_lScene;
		PerModel m_lConstBuffer;
		Eifersucht::ModelViewerCamera m_lCam;
		Eifersucht::SceneChildPtr m_pModelBaseChild;

		SceneGraph m_Graph;

	};
};